local types = {
	fire = true,
	cold = true,
	ice = true,
	electric = true,
	poison = true,
	hurt = true,
}

local DEBUFFS = require "rg_debuff_data"

local SourceModifierList = require "rg_util.sourcemodifierlist"

local Debuff = Class(function(self, inst)
	self.inst = inst

	table.foreach(types, function(k)
		self[k] = {
			time = 0,
			default_time = DEBUFFS[k].time,
			stack = 0,
			fx = nil,

			resist_bool = SourceModifierList(inst, false, SourceModifierList.boolean),
			resist_add  = SourceModifierList(inst, 1, SourceModifierList.additive),
			resist_mult = SourceModifierList(inst, 1, SourceModifierList.multiply),

			damage_mult = SourceModifierList(inst, 1, SourceModifierList.multiply),
			time_mult   = SourceModifierList(inst, 1, SourceModifierList.multiply),

		}
		if k == "electric" then
			self[k]["damage_intervel_mult"] = 1.0
		end
		if DEBUFFS[k].damage then
			self[k]["damage"] = DEBUFFS[k].damage
			self[k]["damage_intervel"] = DEBUFFS[k].interval
			self[k]["damage_time"] = 0
		elseif DEBUFFS[k].dps then
			self[k]["dps"] = DEBUFFS[k].dps
			self[k]["damage"] = self[k].dps*FRAME
			self[k]["damage_intervel"] = FRAME*1.05
			self[k]["damage_time"] = 0
		end
	end)

	self.fx_offset = Vector3(0,0,0)
	self.fx_follow_symbol = nil

	self.inst:StartUpdatingComponent(self)
end)

function Debuff:SetResist(type, method, source, value, key)
	assert(self[type])
	assert(self[type]["resist_"..method])
	self[type]["resist_"..method]:SetModifier(source, value, key)
end

function Debuff:AddResist(type, ...) self:SetResist(type, "add", ...) end

function Debuff:MultResist(type, ...) self:SetResist(type, "mult", ...) end

function Debuff:TagResist(type, ...) self:SetResist(type, "bool", ...) end

function Debuff:ClearResist(type, method, source, value, key)
	assert(self[type])
	assert(self[type]["resist_"..method])
	self[type]["resist_"..method]:RemoveModifier(source, key)
end

function Debuff:CalcTimeDeltaMutiplier(type)
	assert(self[type])
	local r = self[type]
	if r.resist_bool:Get() then
		return math.huge
	else
		return r.resist_mult:Get() * r.resist_add:Get() * 1/r.time_mult:Get()
	end
end

function Debuff:GetMaxStack(type)
	if DEBUFFS[type].stack then
		return self:GetBuffedStack(type) + DEBUFFS[type].stack
	else
		return 1
	end
end

function Debuff:GetBuffedStack(type)
	if DEBUFFS[type].stack_buffed ~= nil then
		return DEBUFFS[type].stack_buffed(self.inst) or 0
	end
end

----------

function Debuff:AddDebuff(type, time)
	--@--
	time = time or 10
	
	assert(self[type])
	assert(time)
	if time == "default" then
		time = self[type].default_time
	end

	if self:CalcTimeDeltaMutiplier(type) > 1e6 then -- immune
		self:OnImmuneDebuff(type)
		return
	end

	local b = self[type]
	local new = b.time <= 0 and b.stack <= 0
	local maxstack = self:GetMaxStack(type)
	if b.stack < maxstack then
		b.stack = b.stack + 1
		if b.stack >= maxstack then
			self:OnGetMaxStack(type)
		end
	else
		b.time = math.max(b.time, time)
	end

	if new == true then
		self:OnGetNewDebuff(type)
	end
end

function Debuff:OnGetNewDebuff(type)
	self.inst:PushEvent("rgevt_get_debuff", {type = type})
	self:SpawnFx(type)
end

function Debuff:OnLoseDebuff(type)
	self.inst:PushEvent("rgevt_lose_debuff", {type = type})
end

function Debuff:OnGetMaxStack(type)
	if type == "cold" then
		-- trigger ice
		if DEBUFFS["ice"].trigger_by_cold_test(self.inst) then
			self.cold.time = 0
			self.cold.stack = 0
			self:AddDebuff("ice", "default")
		end
	end
end

------ DEBUFF FX -------

function Debuff:GetFxOffset(type)
	if self[type.."_fx_offset"] ~= nil then
		return self[type.."_fx_offset"]
	else
		return self["fx_offset"]
	end
end

function Debuff:GetFxFollowSymbol(type)
	if self[type.."_fx_follow_symbol"] ~= nil then
		return self[type.."_fx_follow_symbol"]
	else
		return self["fx_follow_symbol"] 
	end
end

function Debuff:GetFxLevel(type)
	if type == "ice" or type == "cold" then
		if self.inst.components.freezable then
			return self.inst.components.freezable.fxlevel
		end
	elseif type == "fire" then
		if self.inst.components.burnable then
			return self.inst.components.burnable.fxlevel
		end
	else
		error("ValueError: "..type)
	end
end

function Debuff:AttachFx(type, fx)
	if self[type]["fx"] == nil or not self[type]["fx"]:IsValid() then
		self[type]["fx"] = fx
	end

	local x,y,z = self:GetFxOffset(type):Get()
	local symbol = self:GetFxFollowSymbol(type)
	if symbol ~= nil then
		fx.entity:AddFollower()
		fx.Follower:FollowSymbol(self.inst.GUID, symbol, x, y, z)
	else
		self.inst:AddChild(fx)
		fx.Transform:SetPosition(x, y, z)
	end

	fx.target = self.inst
	fx.Transform:SetScale(self.inst.Transform:GetScale()) --?
	if fx.SetLevel ~= nil then
		fx:SetLevel(self:GetFxLevel(type))
	end
	fx:SpawnFn()
	return fx
end

function Debuff:SpawnFx(type)
	local prefab = "rg_debuff_fx_"..type
	local fx = SpawnPrefab(prefab)
	assert(fx, "prefab not exists: ".. prefab)
	self:AttachFx(type, fx)
end

function Debuff:OnDebuffDamage(type)
	if self:CalcTimeDeltaMutiplier(type) > 1e6 then
		return
	end

	local damage = self[type].damage
	self.inst.components.health:DoDelta(-damage, nil, "rg_debuff_"..type)
	if type == "electric" then
		if self[type].fx and self[type].fx:IsValid() then
			self[type].fx:PulseFn()
		end
		--@-- 尝试触发硬直
	end
end

function Debuff:OnUpdate(dt)
	for k in pairs(types)do
		local b = self[k]
		if b.time > 0 then
			b.time = b.time - dt * self:CalcTimeDeltaMutiplier(k)
			if b.time <= 0 then
				if b.stack > 0 then
					b.stack = b.stack - 1
					b.time = b.time + b.default_time
					-- damage
					if b.damage then
						b.damage_time = b.damage_time + dt
						if b.damage_time >= b.damage_intervel * b.damage_intervel_mult then
							b.damage_time = 0
							self:OnDebuffDamage(k)
						end
					end
				else
					self:OnLoseDebuff(k)
				end
			end
		end
	end
end

function Debuff:DebugInfo()
	local info = {}
	for k in pairs(types)do
		info[k] = {
			time = self[k].time,
			stack = self[k].stack or "/",
			time_mult = self:CalcTimeDeltaMutiplier(k),
		}
	end
	return info
end

return Debuff